/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.gui.workflow.parts; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.Platform; import org.eclipse.draw2d.ChopboxAnchor; import org.eclipse.draw2d.ConnectionAnchor; import org.eclipse.draw2d.EllipseAnchor; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.ImageFigure; import org.eclipse.draw2d.Label; import org.eclipse.draw2d.LineBorder; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.gef.CompoundSnapToHelper; import org.eclipse.gef.ConnectionEditPart; import org.eclipse.gef.EditPolicy; import org.eclipse.gef.GraphicalEditPart; import org.eclipse.gef.NodeEditPart; import org.eclipse.gef.Request; import org.eclipse.gef.RequestConstants; import org.eclipse.gef.SnapToGeometry; import org.eclipse.gef.SnapToGrid; import org.eclipse.gef.SnapToHelper; import org.eclipse.gef.commands.Command; import org.eclipse.gef.editparts.AbstractGraphicalEditPart; import org.eclipse.gef.editpolicies.GraphicalNodeEditPolicy; import org.eclipse.gef.requests.CreateConnectionRequest; import org.eclipse.gef.requests.ReconnectRequest; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.views.properties.IPropertySource; import de.rcenvironment.core.component.api.ComponentUtils; import de.rcenvironment.core.component.integration.ToolIntegrationConstants; import de.rcenvironment.core.component.integration.ToolIntegrationContextRegistry; import de.rcenvironment.core.component.model.api.ComponentInterface; import de.rcenvironment.core.component.model.api.ComponentShape; import de.rcenvironment.core.component.model.api.ComponentSize; import de.rcenvironment.core.component.model.spi.PropertiesChangeSupport; import de.rcenvironment.core.component.validation.api.ComponentValidationMessage; import de.rcenvironment.core.component.validation.api.ComponentValidationMessageStore; import de.rcenvironment.core.component.workflow.model.api.WorkflowDescription; import de.rcenvironment.core.component.workflow.model.api.WorkflowNode; import de.rcenvironment.core.gui.resources.api.ColorManager; import de.rcenvironment.core.gui.resources.api.ComponentImageManager; import de.rcenvironment.core.gui.resources.api.ImageManager; import de.rcenvironment.core.gui.resources.api.StandardColors; import de.rcenvironment.core.gui.resources.api.StandardImages; import de.rcenvironment.core.gui.workflow.editor.WorkflowEditorAction; import de.rcenvironment.core.gui.workflow.editor.commands.ConnectionDrawCommand; import de.rcenvironment.core.gui.workflow.editor.properties.ComponentPropertySource; import de.rcenvironment.core.gui.workflow.editor.validator.WorkflowDescriptionValidationUtils; import de.rcenvironment.core.utils.incubator.ServiceRegistry; import de.rcenvironment.core.utils.incubator.ServiceRegistryAccess; /** * Part representing a {@link WorkflowNode}. * * <p> * For information about how the validation works, see {@link WorkflowNodeValidatorSupport}. * </p> * * @author Heinrich Wendel * @author Christian Weiss * @author Sascha Zur * @author Doreen Seider * @author Jascha Riedel (#13765) */ public class WorkflowNodePart extends AbstractGraphicalEditPart implements PropertyChangeListener, NodeEditPart { /** * The width and height of a small workflow node's bounds. Must be divisible by two after 1 is subtracted. */ public static final int SMALL_WORKFLOW_NODE_WIDTH = 41; /** * The width and height of a medium sized workflow node. Must be divisible by four after 1 is subtracted. */ public static final int WORKFLOW_NODE_WIDTH = 81; private static final int MAX_LABELTEXT_SIZE = 30; private static final String LABEL_TEXT_SEPARATOR = "..."; private static final int MAX_LABEL_WIDTH = 73; private static final Image ERROR_IMAGE = ImageManager.getInstance().getSharedImage(StandardImages.ERROR_16); private static final Image WARNING_IMAGE = ImageManager.getInstance().getSharedImage(StandardImages.WARNING_16); private static final Image LOCAL_IMAGE = ImageManager.getInstance().getSharedImage(StandardImages.LOCAL); private static final Image IMITATION_MODE_IMAGE = ImageManager.getInstance().getSharedImage(StandardImages.IMITATION_MODE); private static final Image DEPRECATED_IMAGE = ImageManager.getInstance().getSharedImage(StandardImages.DEPRECATED); private Image imageToDispose = null; private final IFigure errorFigure = new ImageFigure(ERROR_IMAGE); { final int offset = 2; final int size = 16; errorFigure.setBounds(new Rectangle(offset, offset, size, size)); errorFigure.setVisible(false); } private final IFigure warningFigure = new ImageFigure(WARNING_IMAGE); { final int offsetX = 62; final int offsetY = 2; final int size = 16; warningFigure.setBounds(new Rectangle(offsetX, offsetY, size, size)); warningFigure.setVisible(false); } private final IFigure informationFigure = new ImageFigure(LOCAL_IMAGE); { final int offsetX = 62; final int offsetY = 62; final int size = 16; informationFigure.setBounds(new Rectangle(offsetX, offsetY, size, size)); informationFigure.setToolTip(new Label(Messages.localExecutionOnly)); informationFigure.setVisible(false); } private final IFigure deprecatedFigure = new ImageFigure(DEPRECATED_IMAGE); { final int offsetX = 23; final int offsetY = 17; final int size = 32; deprecatedFigure.setBounds(new Rectangle(offsetX, offsetY, size, size)); deprecatedFigure.setToolTip(new Label(Messages.deprecated)); deprecatedFigure.setVisible(false); } private String currentLabel = ""; private final ToolIntegrationContextRegistry toolIntegrationRegistry; public WorkflowNodePart() { ServiceRegistryAccess serviceRegistryAccess = ServiceRegistry.createAccessFor(this); toolIntegrationRegistry = serviceRegistryAccess.getService(ToolIntegrationContextRegistry.class); } @Override public void activate() { super.activate(); Display.getDefault().syncExec(new Runnable() { @Override public void run() { WorkflowDescriptionValidationUtils.validateComponent(getWorkflowNode(), false); updateValid(); } }); ((PropertiesChangeSupport) getModel()).addPropertyChangeListener(this); } @Override public void deactivate() { super.deactivate(); // TODO is this the correct method to call the disposal of objects? if (imageToDispose != null) { imageToDispose.dispose(); } ((PropertiesChangeSupport) getModel()).removePropertyChangeListener(this); } private List<ComponentValidationMessage> getValidationMessages() { final List<ComponentValidationMessage> result = ComponentValidationMessageStore.getInstance() .getMessagesByComponentId(getWorkflowNode().getIdentifier()); return result; } private String getValidationMessageText(final ComponentValidationMessage.Type type) { final StringBuilder builder = new StringBuilder(); for (final ComponentValidationMessage message : getValidationMessages()) { if (type == null || type == message.getType()) { String messageText = message.getAbsoluteMessage(); if (messageText == null || messageText.isEmpty()) { final String property = message.getProperty(); final String relativeMessage = message.getRelativeMessage(); if (property == null || property.isEmpty()) { messageText = relativeMessage; } else { messageText = de.rcenvironment.core.utils.common.StringUtils.format("%s: %s", property, relativeMessage); } } builder.append("- "); builder.append(messageText); builder.append("\n\n"); } } return builder.toString().trim(); } /** * Updates the visual indicators for {@link ComponentValidationMessage}s and refreshes the graphical representation of this * {@link WorkflowNodePart}. * */ public void updateValid() { final String errorText = getValidationMessageText(ComponentValidationMessage.Type.ERROR); errorFigure.setVisible(!errorText.isEmpty()); errorFigure.setToolTip(new Label(errorText)); final String warningText = getValidationMessageText(ComponentValidationMessage.Type.WARNING); warningFigure.setVisible(!warningText.isEmpty()); warningFigure.setToolTip(new Label(warningText)); if (!getWorkflowNode().isEnabled()) { errorFigure.setVisible(false); warningFigure.setVisible(false); } refresh(); refreshVisuals(); // outcommented because it is likely to cause Mantis Issue #0014726; seeb_ol, November 23, 2016 // if (errorText.equals("") && warningText.equals("")) { // getWorkflowNode().setValid(true); // } } protected WorkflowNode getWorkflowNode() { return (WorkflowNode) getModel(); } @Override protected IFigure createFigure() { final IFigure figure = createBaseFigure(); ComponentInterface ci = ((WorkflowNode) getModel()).getComponentDescription().getComponentInstallation() .getComponentRevision().getComponentInterface(); if (ci.getShape() == ComponentShape.CIRCLE) { final int size = 16; final int newOffset = 3; errorFigure.setBounds(new Rectangle(newOffset, newOffset, size, size)); warningFigure.setBounds(new Rectangle(newOffset, newOffset, size, size)); final int localX = 22; final int localY = 20; informationFigure.setBounds(new Rectangle(localX, localY, size, size)); } else if (ci.getSize() == ComponentSize.SMALL) { final int size = 16; final int newOffsetX = 22; final int newOffsetY = 1; warningFigure.setBounds(new Rectangle(newOffsetX, newOffsetY, size, size)); final int localX = 22; final int localY = 22; informationFigure.setBounds(new Rectangle(localX, localY, size, size)); } figure.add(errorFigure); figure.add(warningFigure); figure.add(informationFigure); figure.add(deprecatedFigure); return figure; } protected IFigure createBaseFigure() { Image image = null; ComponentInterface ci = ((WorkflowNode) getModel()).getComponentDescription().getComponentInstallation() .getComponentRevision().getComponentInterface(); if (ci.getSize() == ComponentSize.SMALL) { image = ComponentImageManager.getInstance().getIcon24Image(ci); } else { image = ComponentImageManager.getInstance().getIcon32Image(ci); } if (image == null) { if (ci.getIdentifier().startsWith(ComponentUtils.MISSING_COMPONENT_PREFIX)) { if (ci.getSize() == ComponentSize.SMALL) { image = ImageManager.getInstance().getSharedImage(StandardImages.RCE_LOGO_24_GREY); } else { image = ImageManager.getInstance().getSharedImage(StandardImages.RCE_LOGO_32_GREY); } } else { if (ci.getSize() == ComponentSize.SMALL) { image = ImageManager.getInstance().getSharedImage(StandardImages.RCE_LOGO_24); } else { image = ImageManager.getInstance().getSharedImage(StandardImages.RCE_LOGO_32); } } } Color color = getColor(ci); if (ci.getSize() == ComponentSize.SMALL) { IconLabel iconLabel = new IconLabel(image, ci); iconLabel.setOpaque(true); iconLabel.setBorder(null); iconLabel.setBackgroundColor(color); return iconLabel; } else { final String labelText = ((WorkflowNode) getModel()).getName(); final Label label = new Label(labelText, image); label.setTextPlacement(PositionConstants.SOUTH); label.setBorder(new LineBorder()); label.setOpaque(true); label.setBackgroundColor(color); return label; } } private Color getColor(ComponentInterface ci) { if (!getWorkflowNode().isEnabled()) { return ColorManager.getInstance().getSharedColor(StandardColors.RCE_GREY); } else if (ci.getColor() == null) { return ColorManager.getInstance().getSharedColor(StandardColors.RCE_COLOR_2); } else if (Boolean.valueOf(((WorkflowNode) getModel()).getConfigurationDescription() .getConfigurationValue(ToolIntegrationConstants.KEY_IS_MOCK_MODE))) { return ColorManager.getInstance().getSharedColor(StandardColors.RCE_IMITATION); } else { // TODO There is currently only one ComponentColor specified. // However, the ComponentColor class doesn't store RGB values. // Therefore, the mapping is performed here. return ColorManager.getInstance().getSharedColor(StandardColors.RCE_COLOR_1); } } /** * Class for the small workflow components. * * @author Sascha Zur */ private class IconLabel extends Label { private static final int OFFSET_SMALL_SQUARE_COMPONENT_ICON_X = 8; private static final int OFFSET_SMALL_SQUARE_COMPONENT_ICON_Y = 10; private static final int OFFSET_SMALL_CIRCLE_COMPONENT_ICON = 9; private final Image icon; private final ComponentInterface ci; IconLabel(Image icon, ComponentInterface ci) { this.icon = icon; this.ci = ci; } @Override public void paintFigure(Graphics graphics) { int offsetX; int offsetY; if (ci.getShape() == ComponentShape.CIRCLE) { offsetX = OFFSET_SMALL_CIRCLE_COMPONENT_ICON; offsetY = OFFSET_SMALL_CIRCLE_COMPONENT_ICON; graphics.fillOval(this.getBounds()); graphics.setAntialias(SWT.ON); Rectangle b = this.getBounds().getCopy(); b.width--; b.height--; graphics.drawOval(b); graphics.setAntialias(SWT.OFF); } else { offsetX = OFFSET_SMALL_SQUARE_COMPONENT_ICON_X; offsetY = OFFSET_SMALL_SQUARE_COMPONENT_ICON_Y; graphics.fillRectangle(this.getBounds()); Rectangle b = this.getBounds().getCopy(); b.width--; b.height--; graphics.drawRectangle(b); } graphics.drawImage(icon, new Point(this.getLocation().x + offsetX, this.getLocation().y - 1 + offsetY)); } } protected void setTooltipText() { getFigure().setToolTip(new Label(generateTooltipText())); } protected String generateTooltipText() { WorkflowNode node = (WorkflowNode) getModel(); String enabled = "enabled"; if (!node.isEnabled()) { enabled = "disabled"; } else if (node.getComponentDescription().getIdentifier().startsWith(ComponentUtils.MISSING_COMPONENT_PREFIX)) { enabled = "not available"; } if (Boolean.valueOf( node.getConfigurationDescription().getConfigurationValue(ToolIntegrationConstants.KEY_IS_MOCK_MODE))) { enabled += " (imitation mode)"; } return generateTooltipTextBase(node) + ": " + enabled; } protected String generateTooltipTextBase(WorkflowNode node) { if (node.getComponentDescription().getVersion() != null && toolIntegrationRegistry.hasId(node.getComponentDescription().getIdentifier())) { return de.rcenvironment.core.utils.common.StringUtils.format("%s - %s (%s)", node.getName(), node.getComponentDescription().getName(), node.getComponentDescription().getVersion()); } else { return de.rcenvironment.core.utils.common.StringUtils.format("%s - %s", node.getName(), node.getComponentDescription().getName()); } } @Override public void refreshVisuals() { Point loc = new Point(((WorkflowNode) getModel()).getX(), ((WorkflowNode) getModel()).getY()); int width = WORKFLOW_NODE_WIDTH; int height = WORKFLOW_NODE_WIDTH; if (((WorkflowNode) getModel()).getComponentDescription().getComponentInstallation().getComponentRevision() .getComponentInterface().getSize() == ComponentSize.SMALL) { width = SMALL_WORKFLOW_NODE_WIDTH; height = SMALL_WORKFLOW_NODE_WIDTH; } Rectangle r = new Rectangle(loc, new Dimension(width, height)); final Label label = (Label) getFigure(); String labelText = getWorkflowNode().getName(); // try to abbreviate label just, when the name of the workflow has // changed in the model if (!currentLabel.equals(getWorkflowNode().getName())) { abbreviateLabel(label, labelText, MAX_LABELTEXT_SIZE); } // remember original workflow node name from model currentLabel = labelText; setTooltipText(); if (((WorkflowNode) getModel()).getComponentDescription().canOnlyBeExecutedLocally()) { ((ImageFigure) informationFigure).setImage(LOCAL_IMAGE); informationFigure.setToolTip(new Label(Messages.localExecutionOnly)); informationFigure.setVisible(true); } else if (Boolean.valueOf(((WorkflowNode) getModel()).getConfigurationDescription() .getConfigurationValue(ToolIntegrationConstants.KEY_IS_MOCK_MODE))) { ((ImageFigure) informationFigure).setImage(IMITATION_MODE_IMAGE); informationFigure.setToolTip(new Label(Messages.imitationMode)); informationFigure.setVisible(true); } else { informationFigure.setVisible(false); } deprecatedFigure.setVisible(((WorkflowNode) getModel()).getComponentDescription().getComponentInstallation() .getComponentRevision().getComponentInterface().getIsDeprecated()); getFigure().setBackgroundColor(getColor(((WorkflowNode) getModel()).getComponentDescription() .getComponentInstallation().getComponentRevision().getComponentInterface())); ((GraphicalEditPart) getParent()).setLayoutConstraint(this, label, r); } private String abbreviateLabel(Label label, String labelText, int currentLength) { label.setText(labelText); if (label.getFont() == null) { label.setFont(Display.getDefault().getSystemFont()); } if (label.getTextBounds().width > MAX_LABEL_WIDTH) { String shorterLabelText = ""; shorterLabelText = StringUtils.abbreviateMiddle(label.getText(), LABEL_TEXT_SEPARATOR, currentLength); currentLength--; label.setText(shorterLabelText); abbreviateLabel(label, shorterLabelText, currentLength); } return label.getText(); } @Override public void propertyChange(PropertyChangeEvent evt) { String prop = evt.getPropertyName(); ((WorkflowNode) getModel()).setValid(false); if (WorkflowNode.PROPERTY_COMMUNICATION_NODE.equals(prop)) { refreshVisuals(); } else if (WorkflowNode.PROPERTY_NODE_ATTRIBUTES.equals(prop)) { refreshVisuals(); } } /** * Method called by the WorkflowPart to refresh the connections. */ public void refreshConnections() { refreshSourceConnections(); refreshTargetConnections(); } @Override public ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connection) { // handle reconnections if (connection != null) { if (connection.getSource() != null && connection.getTarget() != null) { if (connection.getSource().equals(connection.getTarget())) { return new ReconnectionSourceAnchor(getFigure()); } } } if (((WorkflowNode) getModel()).getComponentDescription().getComponentInstallation().getComponentRevision() .getComponentInterface().getShape() == ComponentShape.CIRCLE) { return new EllipseAnchor(getFigure()); } else { return new ChopboxAnchor(getFigure()); } } @Override public ConnectionAnchor getSourceConnectionAnchor(Request request) { if (((WorkflowNode) getModel()).getComponentDescription().getComponentInstallation().getComponentRevision() .getComponentInterface().getShape() == ComponentShape.CIRCLE) { return new EllipseAnchor(getFigure()); } else { return new ChopboxAnchor(getFigure()); } } @Override public ConnectionAnchor getTargetConnectionAnchor(ConnectionEditPart connection) { // handle reconnections if (connection != null) { if (connection.getSource() != null && connection.getTarget() != null) { if (connection.getSource().equals(connection.getTarget())) { return new ReconnectionTargetAnchor(getFigure()); } } } if (((WorkflowNode) getModel()).getComponentDescription().getComponentInstallation().getComponentRevision() .getComponentInterface().getShape() == ComponentShape.CIRCLE) { return new EllipseAnchor(getFigure()); } else { return new ChopboxAnchor(getFigure()); } } @Override public ConnectionAnchor getTargetConnectionAnchor(Request request) { if (((WorkflowNode) getModel()).getComponentDescription().getComponentInstallation().getComponentRevision() .getComponentInterface().getShape() == ComponentShape.CIRCLE) { return new EllipseAnchor(getFigure()); } else { return new ChopboxAnchor(getFigure()); } } @Override protected List<ConnectionWrapper> getModelSourceConnections() { List<ConnectionWrapper> sourceConnections = new ArrayList<>(); for (ConnectionWrapper c : ((WorkflowPart) getParent()).getConnections()) { if (c.getSource().equals(getModel())) { sourceConnections.add(c); } } return sourceConnections; } @Override protected List<ConnectionWrapper> getModelTargetConnections() { List<ConnectionWrapper> targetConnections = new ArrayList<>(); for (ConnectionWrapper c : ((WorkflowPart) getParent()).getConnections()) { if (c.getTarget().equals(getModel())) { targetConnections.add(c); } } return targetConnections; } @Override public Object getAdapter(@SuppressWarnings("rawtypes") Class type) { // Enable Snap to grid/geometry in wf editor for the nodepart. if (type == SnapToHelper.class) { List<SnapToHelper> helpers = new ArrayList<>(); if (Boolean.TRUE.equals(getViewer().getProperty(SnapToGeometry.PROPERTY_SNAP_ENABLED))) { helpers.add(new SnapToGeometry(this)); } if (Boolean.TRUE.equals(getViewer().getProperty(SnapToGrid.PROPERTY_GRID_ENABLED))) { helpers.add(new SnapToGrid(this)); } if (helpers.size() == 0) { return null; } else { return new CompoundSnapToHelper(helpers.toArray(new SnapToHelper[0])); } } else if (type == IPropertySource.class && getModel() instanceof WorkflowNode) { return new ComponentPropertySource(getViewer().getEditDomain().getCommandStack(), (WorkflowNode) getModel()); } return super.getAdapter(type); } @Override public void performRequest(Request req) { if (req.getType().equals(RequestConstants.REQ_OPEN)) { try { PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage() .showView("org.eclipse.ui.views.PropertySheet"); } catch (PartInitException e) { throw new RuntimeException(e); } } } @Override protected void createEditPolicies() { deactivateEditPolicies(); // allow connections installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, new ConnectEditPolicy()); } /** * Checks for registered editor actions and if it finds one for the selected component, this is invoked. As no action is currently * required here, the method is deprecated. */ @Deprecated private void performDefaultAction() { IConfigurationElement[] confElements = Platform.getExtensionRegistry() .getConfigurationElementsFor("de.rcenvironment.core.gui.workflow.editorActions"); //$NON-NLS-1$ for (final IConfigurationElement confElement : confElements) { WorkflowNode node = getWorkflowNode(); if (node.getComponentDescription().getIdentifier().matches(confElement.getAttribute("component")) && confElement.getAttribute("default") != null && Boolean.TRUE.toString().matches(confElement.getAttribute("default"))) { //$NON-NLS-1$ final WorkflowEditorAction action; try { Object actionObject = confElement.createExecutableExtension("class"); if (!(actionObject instanceof WorkflowEditorAction)) { throw new RuntimeException(de.rcenvironment.core.utils.common.StringUtils.format( "Class in attribute 'class' is not a subtype of '%s'.", WorkflowEditorAction.class.getName())); } action = (WorkflowEditorAction) actionObject; } catch (CoreException e) { throw new RuntimeException(e); } action.setWorkflowNode(node); action.performAction(); break; } } } /** * EditPolicy that allows connections. * * @author Heinrich Wendel */ class ConnectEditPolicy extends GraphicalNodeEditPolicy { @Override protected Command getConnectionCompleteCommand(CreateConnectionRequest request) { ConnectionDrawCommand cmd = (ConnectionDrawCommand) request.getStartCommand(); cmd.setTarget((WorkflowNode) getHost().getModel()); return cmd; } @Override protected Command getConnectionCreateCommand(CreateConnectionRequest request) { WorkflowNode source = (WorkflowNode) getHost().getModel(); ConnectionDrawCommand cmd = new ConnectionDrawCommand((WorkflowDescription) getParent().getModel(), source); request.setStartCommand(cmd); return cmd; } @Override protected Command getReconnectSourceCommand(ReconnectRequest request) { return null; } @Override protected Command getReconnectTargetCommand(ReconnectRequest request) { return null; } } /** * Anchor for the depiction of reconnections, i.e. connections from one component to the same. Handles the source anchor. * * @author Oliver Seebach */ class ReconnectionSourceAnchor extends ChopboxAnchor { ReconnectionSourceAnchor(IFigure figure) { super.setOwner(figure); } @Override protected Rectangle getBox() { int x = getOwner().getBounds().getCenter().x - 4; // - 3 to make // sure, the // connection is // routed to the // left int y = getOwner().getBounds().getCenter().y - getOwner().getBounds().height / 2 - 1; return new Rectangle(new Point(x, y), new Point(x, y)); } } /** * Anchor for the depiction of reconnections, i.e. connections from one component to the same. Handles the target anchor. * * @author Oliver Seebach */ class ReconnectionTargetAnchor extends ChopboxAnchor { ReconnectionTargetAnchor(IFigure figure) { super.setOwner(figure); } @Override protected Rectangle getBox() { int x = getOwner().getBounds().getCenter().x - 4; // - 3 to make // sure, the // connection is // routed to the // left int y = getOwner().getBounds().getCenter().y + getOwner().getBounds().height / 2 + 1; return new Rectangle(new Point(x, y), new Point(x, y)); } } }